#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <gmp.h>
#include <time.h>
#include <pthread.h>

pthread_t thread_an1;
pthread_t thread_bn1;
pthread_t thread_tn1;
pthread_t thread_pn1;
pthread_t thread_swap_an1;
pthread_t thread_swap_bn1;
pthread_t thread_swap_tn1;
pthread_t thread_swap_pn1;
pthread_t thread_init_an1;
pthread_t thread_init_bn1;
pthread_t thread_init_pn1;
pthread_t thread_init_tn1;

/* Váriaveis do algoritmo */
mpf_t an, an1;
mpf_t bn, bn1;
mpf_t tn, tn1;
mpf_t pn, pn1;
mpf_t pi;

long i;
long N;

void *f_an1(void *nada) { 

  //an1 = (an + bn)/2.0;
  mpf_add(an1,an,bn);
  mpf_div_ui(an1,an1,2);

}

void *f_bn1(void *nada) {

  //bn1 = pow(an*bn,1.0/2.0);
  mpf_mul(bn1,an,bn);
  mpf_sqrt(bn1,bn1);

}

void *f_tn1(void *nada) {
  
  //tn1 = tn - pn*(an - an1)*(an - an1);
  mpf_sub(tn1,an,an1);  // tn1 = an - an1
  mpf_mul(tn1,tn1,tn1); // tn1 = (an - an1)^2
  mpf_mul(tn1,tn1,pn);  // tn1 = pn*(an - an1)^2    
  mpf_sub(tn1,tn,tn1);  

}

void *f_pn1(void *nada) {
  
  //pn1 = 2*pn;
  mpf_mul_ui(pn1,pn,2);

}

void *f_swap_an1(void *nada) {
  //an = an1;
  mpf_set(an,an1);
}

void *f_swap_bn1(void *nada) {
  //bn = bn1;
  mpf_set(bn,bn1);
}


void *f_swap_tn1(void *nada) {
  //tn = tn1;
  mpf_set(tn,tn1);
}

void *f_swap_pn1(void *nada) {
  //pn = pn1;
  mpf_set(pn,pn1);

}

void *init_an(void *nada) {
  mpf_init_set_ui(an,1);
}

void *init_bn(void *nada) {
  mpf_init(bn);
  mpf_sqrt_ui(bn,2);
  mpf_ui_div(bn,1,bn);
}

void *init_pn(void *nada) {
  mpf_init_set_ui(pn,1);
}

void *init_tn(void *nada) {
  mpf_init_set_str(tn,"0.25",10);
}

int main(int argc, char *argv[]) {
  /* Váriaveis para contar o tempo */
  clock_t begin, end;
  int j;

  FILE *pi_file = fopen("pi_gauss_paralelo.txt","w+");

  if(pi_file == NULL) {
    return EXIT_FAILURE;
  }

  if(argc != 2) {
    printf("\nUsage: %s [n_iter].\n",argv[0]);
    exit(-1);
  } else {
    sscanf(argv[1],"%ld",&N);
  }

  mpf_set_default_prec(40000000); /* Precisão necessária para calcular 10 milhões de dígitos */

  begin = clock();
  /* Inicialização das váriaveis */

  if(pthread_create(&thread_init_an1,NULL,init_an,NULL) || pthread_create(&thread_init_bn1,NULL,init_bn,NULL) || pthread_create(&thread_init_tn1,NULL,init_tn,NULL) || pthread_create(&thread_init_pn1,NULL,init_pn,NULL)) {
    printf("Erro na criação da thread.\n");
    return EXIT_FAILURE;
  }
  pthread_join(thread_init_an1,NULL);
  pthread_join(thread_init_bn1,NULL);
  pthread_join(thread_init_pn1,NULL);
  pthread_join(thread_init_tn1,NULL);

  mpf_init(an1);
  mpf_init(bn1);
  mpf_init(tn1);
  mpf_init(pn1);

  mpf_init(pi);

  printf("Inciando cálculo do PI pelo método de Gauss-Legendre com %ld iterações.\n",N);

  i = 0;
  while(i<N) {   
    
    if(pthread_create(&thread_an1,NULL,f_an1,NULL) || pthread_create(&thread_bn1,NULL,f_bn1,NULL) || pthread_create(&thread_pn1,NULL,f_pn1,NULL)) {
      printf("Erro na criação da thread.\n");
      return EXIT_FAILURE;
    }

    pthread_join(thread_an1,NULL);
    pthread_join(thread_bn1,NULL);
    pthread_join(thread_pn1,NULL);

    if(pthread_create(&thread_tn1,NULL,f_tn1,NULL) || pthread_create(&thread_swap_bn1,NULL,f_swap_bn1,NULL)) {
      printf("Erro na criação da thread.\n");
      return EXIT_FAILURE;
    }

    pthread_join(thread_tn1,NULL);
    pthread_join(thread_swap_bn1,NULL);

    if(pthread_create(&thread_swap_an1,NULL,f_swap_an1,NULL) || pthread_create(&thread_swap_tn1,NULL,f_swap_tn1,NULL) || pthread_create(&thread_swap_pn1,NULL,f_swap_pn1,NULL)) {
      printf("Erro na criação da thread.\n");
      return EXIT_FAILURE;
    }      
    
    pthread_join(thread_swap_an1,NULL); 
    pthread_join(thread_swap_pn1,NULL);
    pthread_join(thread_swap_tn1,NULL);
    printf("Iteração: %ld\n",i);


    mpf_add(pi,an,bn);   // pi = an + bn
    mpf_pow_ui(pi,pi,2); // pi = (an + bn)^2
    mpf_div_ui(pi,pi,4); // pi = (an + bn)^2 / 4
    mpf_div(pi,pi,tn);   // pi = (an + bn)^2 / 4 / tn*/
    
    mpf_out_str(pi_file,10,0,pi);
    fprintf(pi_file,"\n");

    i++;
  }

  end = clock();
  printf("\nTempo do cálculo: %lf segundos\n",(double)((end - begin)/CLOCKS_PER_SEC));

  fclose(pi_file);
  mpf_clears(pi,an,bn,tn,pn,an1,bn1,tn1,pn1,NULL);

  pthread_exit(NULL);      

}
